package com.zys.http.processor.spring;

import cn.hutool.core.text.CharSequenceUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.search.GlobalSearchScope;
import com.zys.http.constant.HttpEnum;
import com.zys.http.entity.param.ParamProperty;
import com.zys.http.entity.tree.MethodNodeData;
import com.zys.http.extension.setting.HttpSetting;
import com.zys.http.processor.AbstractKotlinProcessor;
import com.zys.http.tool.ThreadTool;
import com.zys.http.tool.UrlTool;
import com.zys.http.ui.tree.node.MethodNode;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.stubindex.KotlinAnnotationsIndex;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;

import java.util.*;

/**
 * @author zys
 * @since 2025-01-14
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SpringKotlinProcessor extends AbstractKotlinProcessor {
    public static final SpringKotlinProcessor INSTANCE = new SpringKotlinProcessor();

    private static final String SPRING_ANNOTATION_PACKAGE = "org.springframework.web.bind.annotation.";

    @Override
    public boolean isTargetController(PsiElement element) {
        if (!(element instanceof KtClass ktClass)) {
            return false;
        }
        return ktClass.getAnnotationEntries().stream()
                .anyMatch(entry -> isTargetAnnotation(entry, SpringEnums.Controller.CONTROLLER.getClazz()) ||
                        isTargetAnnotation(entry, SpringEnums.Controller.REST_CONTROLLER.getClazz()) ||
                        isTargetAnnotation(entry, HttpSetting.getInstance().getCustomAnno()));
    }

    @Override
    @SuppressWarnings("deprecation")
    public List<? extends PsiElement> moduleControllers(Module module, Project project) {
        Optional<GlobalSearchScope> scopeOp = Optional.of(module).map(Module::getModuleScope);

        Collection<KtAnnotationEntry> entries = ApplicationManager.getApplication().runReadAction((Computable<Collection<KtAnnotationEntry>>) () -> {
            Collection<KtAnnotationEntry> all = new ArrayList<>();
            for (SpringEnums.Controller controller : SpringEnums.Controller.values()) {
                if (controller.equals(SpringEnums.Controller.RESPONSE_BODY)) {
                    continue;
                }
                String shortClassName = controller.getShortClassName();
                all.addAll(scopeOp.map(ms -> KotlinAnnotationsIndex.getInstance().get(shortClassName, project, ms)).orElse(new ArrayList<>()));
            }

            String customAnno = HttpSetting.getInstance().getCustomAnno();
            if (CharSequenceUtil.isNotEmpty(customAnno) && !customAnno.endsWith(".")) {
                String shortName = customAnno.substring(customAnno.lastIndexOf('.') + 1);
                all.addAll(scopeOp.map(ms -> KotlinAnnotationsIndex.getInstance().get(shortName, project, ms)).orElse(new ArrayList<>()));
            }
            return all;
        });

        return entries.stream()
                .map(PsiElement::getParent)
                .map(KtDeclarationModifierList.class::cast)
                .map(KtDeclarationModifierList::getParent)
                .filter(KtClass.class::isInstance)
                .map(KtClass.class::cast)
                .toList();
    }

    @Override
    public List<String> controllerPaths(PsiElement element) {
        Set<? extends PsiElement> entries = controllerAnnotations(element);
        for (PsiElement entry : entries) {
            if (isTargetAnnotation(entry, SpringEnums.Method.REQUEST.getClazz())) {
                return annotationValues(entry, new String[]{VALUE_KEY, "path"});
            }
        }
        return DEFAULT_ANNO_VALUE;
    }

    @Override
    @SneakyThrows
    public boolean isTargetMethod(PsiElement element) {
        if (!(element instanceof KtNamedFunction function)) {
            return false;
        }
        return ReadAction.nonBlocking(() -> function.getAnnotationEntries().stream()
                        .map(KtAnnotationEntry::getShortName)
                        .filter(Objects::nonNull)
                        .map(Name::asString)
                        .map(o -> o.startsWith(SPRING_ANNOTATION_PACKAGE) ? o : SPRING_ANNOTATION_PACKAGE + o)
                        .anyMatch(SpringEnums.Method::contains))
                .submit(ThreadTool.getExecutor())
                .get();
    }

    @Override
    public List<String> methodPaths(PsiElement element) {
        if (!(element instanceof KtNamedFunction function)) {
            return DEFAULT_ANNO_VALUE;
        }
        List<KtAnnotationEntry> entries = function.getAnnotationEntries();
        for (KtAnnotationEntry entry : entries) {
            Name shortName = entry.getShortName();
            if (Objects.isNull(shortName)) {
                continue;
            }
            String annotationName = shortName.asString();
            annotationName = annotationName.startsWith(SPRING_ANNOTATION_PACKAGE) ? annotationName : SPRING_ANNOTATION_PACKAGE + annotationName;
            if (SpringEnums.Method.contains(annotationName)) {
                return annotationValues(entry, new String[]{VALUE_KEY, "path"});
            }
        }
        return DEFAULT_ANNO_VALUE;
    }

    @Override
    public List<MethodNode> methodNodeCreate(PsiElement element, String controllerPath, String contextPath, boolean isFirstCall) {
        if (!(element instanceof KtNamedFunction function)) {
            return Collections.emptyList();
        }
        KtModifierList modifierList = function.getModifierList();
        if (Objects.isNull(modifierList)) {
            return Collections.emptyList();
        }
        List<String> paths = new ArrayList<>();
        HttpEnum.HttpMethod httpMethod = null;
        List<KtAnnotationEntry> entries = function.getAnnotationEntries();
        for (KtAnnotationEntry entry : entries) {
            Name shortName = entry.getShortName();
            if (Objects.isNull(shortName)) {
                continue;
            }
            String annotationName = shortName.asString();
            annotationName = annotationName.startsWith(SPRING_ANNOTATION_PACKAGE) ? annotationName : SPRING_ANNOTATION_PACKAGE + annotationName;
            httpMethod = SpringEnums.Method.get(annotationName);
            if (HttpEnum.HttpMethod.REQUEST.equals(httpMethod)) {
                httpMethod = HttpEnum.HttpMethod.requestMappingConvert(entry);
            }
            if (Objects.nonNull(httpMethod)) {
                paths = annotationValues(entry, new String[]{VALUE_KEY, "path"});
                break;
            }

        }
        if (Objects.isNull(httpMethod)) {
            return Collections.emptyList();
        }
        List<MethodNode> res = new ArrayList<>();
        for (String path : paths) {
            MethodNodeData data = new MethodNodeData(httpMethod, path, controllerPath, contextPath);
            data.setPsiElement(function);
            data.setDescription(methodSwagger(function));
            res.add(new MethodNode(data));
        }
        return res;
    }


    @Override
    public void parameterParse(@NotNull PsiElement element, @NotNull Map<String, ParamProperty> map, boolean isJsonPretty) {
        if (!(element instanceof KtParameter parameter)) {
            return;
        }

        String parameterName = parameter.getName();
        List<KtAnnotationEntry> entries = parameter.getAnnotationEntries().stream().filter(entry -> Objects.nonNull(entry.getShortName())).toList();
        String[] keys = new String[]{VALUE_KEY, NAME_KEY};

        KtAnnotationEntry headerAnno = entries.stream().filter(entry ->
                SpringEnums.Param.REQUEST_HEADER.getClazz().equals(Objects.requireNonNull(entry.getShortName()).asString()) ||
                        SpringEnums.Param.REQUEST_HEADER.getShortName().equals(entry.getShortName().asString())
        ).findFirst().orElse(null);

        if (Objects.nonNull(headerAnno)) {
            ParamProperty property = map.getOrDefault(REQUEST_TYPE_KEY, null);
            if (property == null) {
                map.put(REQUEST_TYPE_KEY, new ParamProperty(HttpEnum.ContentType.APPLICATION_X_FORM_URLENCODED, HttpEnum.ParamUsage.HEADER));
            }
            map.put(parameterName, new ParamProperty("", HttpEnum.ParamUsage.HEADER));
            return;
        }

        // 参数
        KtAnnotationEntry paramAnno = entries.stream().filter(entry ->
                SpringEnums.Param.REQUEST_PARAM.getClazz().equals(Objects.requireNonNull(entry.getShortName()).asString()) ||
                        SpringEnums.Param.REQUEST_PARAM.getShortName().equals(entry.getShortName().asString())
        ).findFirst().orElse(null);
        HttpEnum.ParamUsage paramUsage = HttpEnum.ParamUsage.URL;
        if (Objects.nonNull(paramAnno)) {
            String annotationValue = annotationValue(paramAnno, keys);
            if (CharSequenceUtil.isNotEmpty(annotationValue)) {
                parameterName = annotationValue;
            }
        }

        String canonicalText = Objects.requireNonNull(parameter.getTypeReference()).getText();
        if (canonicalText.contains("org.springframework.web.multipart.MultipartFile") || "MultipartFile".equals(canonicalText)) {
            // 说明是文件
            KtAnnotationEntry partAnno = entries.stream().filter(entry ->
                    SpringEnums.Param.REQUEST_PART.getClazz().equals(Objects.requireNonNull(entry.getShortName()).asString()) ||
                            SpringEnums.Param.REQUEST_PART.getShortName().equals(entry.getShortName().asString())
            ).findFirst().orElse(null);
            if (Objects.nonNull(partAnno)) {
                // @RequestPart 是否有 value 或 name 属性, 如果有会将请求参数名变为那个
                String annotationValue = annotationValue(partAnno, keys);
                if (CharSequenceUtil.isNotEmpty(annotationValue)) {
                    parameterName = annotationValue;
                }
            }

            map.put(REQUEST_TYPE_KEY, new ParamProperty(HttpEnum.ContentType.MULTIPART_FORM_DATA, HttpEnum.ParamUsage.HEADER));
            map.put(parameterName, new ParamProperty("", HttpEnum.ParamUsage.FILE));
            return;
        }

        KtAnnotationEntry pathAnno = entries.stream().filter(entry ->
                SpringEnums.Param.PATH_VARIABLE.getClazz().equals(Objects.requireNonNull(entry.getShortName()).asString()) ||
                        SpringEnums.Param.PATH_VARIABLE.getShortName().equals(entry.getShortName().asString())
        ).findFirst().orElse(null);
        if (Objects.nonNull(pathAnno)) {
            paramUsage = HttpEnum.ParamUsage.PATH;
            String annotationValue = annotationValue(pathAnno, keys);
            if (CharSequenceUtil.isNotEmpty(annotationValue)) {
                parameterName = annotationValue;
            }
        }

        Object paramDefaultTypeValue = typeDefaultValue(parameter.getTypeReference(), parameter.getProject());
        if (Objects.isNull(paramDefaultTypeValue)) {
            map.put(REQUEST_TYPE_KEY, new ParamProperty(HttpEnum.ContentType.APPLICATION_X_FORM_URLENCODED, HttpEnum.ParamUsage.HEADER));
            return;
        }
        boolean isJson = entries.stream().anyMatch(entry -> isTargetAnnotation(entry, SpringEnums.Param.REQUEST_BODY.getClazz()));

        typeParseResult(parameterName, paramDefaultTypeValue, isJson, isJsonPretty, map, paramUsage);
    }

    @Override
    public List<PostmanItem> postmanMethodItems(PsiElement element, String modulePath, String controllerPath) {
        if (!isTargetMethod(element)) {
            return Collections.emptyList();
        }
        KtNamedFunction function = (KtNamedFunction) element;
        KtModifierList modifierList = function.getModifierList();
        if (Objects.isNull(modifierList)) {
            return null;
        }
        // 方法名
        String methodSwagger = methodSwagger(function);
        methodSwagger = CharSequenceUtil.isEmpty(methodSwagger) ? function.getName() : methodSwagger;

        for (KtAnnotationEntry entry : function.getAnnotationEntries()) {
            Name shortName = entry.getShortName();
            if (Objects.isNull(shortName)) {
                continue;
            }
            String annotationName = shortName.asString();
            annotationName = annotationName.startsWith(SPRING_ANNOTATION_PACKAGE) ? annotationName : SPRING_ANNOTATION_PACKAGE + annotationName;
            HttpEnum.HttpMethod httpMethod = SpringEnums.Method.get(annotationName);
            if (HttpEnum.HttpMethod.REQUEST.equals(httpMethod)) {
                httpMethod = HttpEnum.HttpMethod.requestMappingConvert(entry);
            }
            if (Objects.isNull(httpMethod)) {
                continue;
            }

            // 请求 uri
            List<PostmanItem> res = new ArrayList<>();
            List<String> paths = methodPaths(entry);
            for (String path : paths) {
                PostmanItem item = postmanItemCreate(methodSwagger, httpMethod.name(), UrlTool.buildMethodUri(modulePath, controllerPath, path));
                // 请求参数类型
                this.postmanItemPropertyCompletion(function, item);
                res.add(item);
            }
            return res;
        }
        return Collections.emptyList();
    }
}
